package com.njtoyo.taxi.admin.logic.business.order_refund;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.njtoyo.taxi.admin.library.payment.cmb.CmbPayClient;
import com.njtoyo.taxi.admin.logic.business.driver.DriverLogic;
import com.njtoyo.taxi.admin.mapper.master.entity.*;
import com.njtoyo.taxi.admin.mapper.master.enums.RideCategory;
import com.njtoyo.taxi.admin.mapper.master.mapper.*;
import com.njtoyo.taxi.admin.utils.Identifier;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;

/**
 * 退款服务优化版
 */
@Service
@Slf4j
public class RefundService {

    // 常量定义
    private static final String REFUND_TYPE_ALL = "ALL";
    private static final String REFUND_TYPE_PART = "PART";
    private static final String PAYMENT_SUCCESS = "SUCCESS";
    private static final String PAYMENT_CATEGORY_REALTIME = "realtime";
    private static final BigDecimal HUNDRED = new BigDecimal(100);
    private static final int REFUND_LOG_SUCCESS_STATE = 2;

    @Autowired
    private RideMapper rideMapper;
    @Autowired
    private RideOrderMapper rideOrderMapper;
    @Autowired
    private RealtimeCallMapper realtimeCallMapper;
    @Autowired
    private AppointmentCallMapper appointmentCallMapper;
    @Autowired
    private CallPrepayOrderMapper callPrepayOrderMapper;
    @Autowired
    private CallPrepayOrderRefundLogMapper callPrepayOrderRefundLogMapper;
    @Autowired
    private RideRefundLogMapper rideRefundLogMapper;
    @Autowired
    private RideOrderDetailMapper rideOrderDetailMapper;
    @Autowired
    private CouponMapper couponMapper;
    @Autowired
    private DriverLogic driverLogic;

    /**
     * 退款入口方法（事务管理）
     */
//    @Transactional(rollbackFor = Exception.class)
    public boolean refund(Long callId, Long rideId, RideCategory category, String type,
                          BigDecimal willRefundAmount, String refundType,
                          String refundReason, String operator) {
        try {
            RefundContext context = prepareRefundContext(rideId, callId, category, operator, willRefundAmount);
            validateRefundRequest(context, willRefundAmount, refundType);

            switch (refundType) {
                case REFUND_TYPE_ALL:
                    processFullRefund(context, refundReason, operator);
                    break;
                case REFUND_TYPE_PART:
                    processPartialRefund(context, willRefundAmount, refundReason, operator);
                    break;
                default:
                    throw new RefundException("不支持的退款类型: " + refundType);
            }
            return true;
        } catch (RefundException e) {
            log.error("退款处理失败: {}", e.getMessage());
            throw new BusinessException(e.getMessage()); // 自定义业务异常
        }
    }

    /**
     * 准备退款上下文
     */
    private RefundContext prepareRefundContext(Long rideId, Long callId, RideCategory category, String operator, BigDecimal willRefundAmount) throws RefundException {
        Ride ride = getRideById(rideId);
        RideOrder rideOrder = getRideOrderByRideId(rideId);
        if (ObjectUtil.isNull(rideOrder.getRefundAmount())) {
            rideOrder.setRefundAmount(BigDecimal.ZERO);
        }
        if (ObjectUtil.isNull(rideOrder.getDriverRefundAmount())) {
            rideOrder.setDriverRefundAmount(BigDecimal.ZERO);
        }
        List<RideRefundLog> refundLogs = getRefundLogs(rideId);
        BigDecimal driverBalance = driverLogic.getDriverBalance(ride.getDriverId());
        if (ObjectUtil.isNull(driverBalance)) {
            driverBalance = BigDecimal.ZERO;
        }
        RefundContext context = RefundContext.builder()
                .ride(ride)
                .rideOrder(rideOrder)
                .driverBalance(driverBalance)
                .refundedAmount(calculateRefundedAmount(refundLogs))
                .build();


        if (RideCategory.realtime == category) {
            RealtimeCall realtimeCall = (RealtimeCall) getCall(callId, "realtime");
            CallPrepayOrder prepayOrder = getPrepayOrder(callId, "realtime");
            BigDecimal prepayRefund = calculatePrepayRefund(prepayOrder);

            context.setRealtimeCall(realtimeCall)
                    .setApplyTime(DateUtil.now())
                    .setPrepayOrder(prepayOrder)
                    .setCategory("realtime")
                    .setPrepayRefundAmount(prepayRefund)
                    .setOperator(operator)
                    .setOrderRealAmount(rideOrder.getTotalPaid());
        } else if (RideCategory.appointment == category) {
            AppointmentCall appointment = (AppointmentCall) getCall(callId, "appointment");
            CallPrepayOrder prepayOrder = getPrepayOrder(callId, "appointment");
            BigDecimal prepayRefund = calculatePrepayRefund(prepayOrder);

            context.setAppointmentCall(appointment)
                    .setApplyTime(DateUtil.now())
                    .setPrepayOrder(prepayOrder)
                    .setCategory("appointment")
                    .setPrepayRefundAmount(prepayRefund)
                    .setOperator(operator)
                    .setOrderRealAmount(calculateOrderRealAmount(rideOrder, prepayOrder, prepayRefund));
        } else {
            throw new RefundException("不支持的退款类别: " + category);
        }
        if (CollUtil.isNotEmpty(refundLogs)) {
            context.setDriverRefundRatio(refundLogs.get(0).getDriverRefundRatio());
            context.setPlatformRefundRatio(refundLogs.get(0).getPlatformRefundRatio());
            context.setCompanyRefundRatio(refundLogs.get(0).getCompanyRefundRatio());
        } else {//计算退款比率
            RideOrderDetail rideOrderDetail = rideOrderDetailMapper.selectOne(Wrappers.<RideOrderDetail>lambdaQuery()
                    .eq(RideOrderDetail::getRideId, rideId));
            BigDecimal infoServiceAmount = BigDecimal.ZERO;
            if (ObjectUtil.isNull(rideOrderDetail) || ObjectUtil.isNull(rideOrderDetail.getPlatformEarning())
                    || !(rideOrderDetail.getPlatformEarning().compareTo(BigDecimal.ZERO) > 0)) {
                infoServiceAmount = rideOrder.getInfoServiceAmount();
            } else {
                infoServiceAmount = rideOrderDetail.getPlatformEarning();
            }
            BigDecimal infoRatio = infoServiceAmount.divide(context.getOrderRealAmount(), 4, RoundingMode.HALF_UP);
            BigDecimal driverRate = BigDecimal.ONE.subtract(infoRatio);
            context.setDriverRefundRatio(driverRate);
            context.setPlatformRefundRatio(infoRatio);
        }
        return context;
    }

    /**
     * 验证退款请求
     */
    private void validateRefundRequest(RefundContext context,
                                       BigDecimal willRefundAmount,
                                       String refundType) throws RefundException {
        //当前可退金额
        BigDecimal availableAmount = context.getOrderRealAmount().subtract(context.getRefundedAmount());

        if (REFUND_TYPE_PART.equals(refundType)) {
            if (willRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
                throw new RefundException("退款金额必须大于零");
            }
            if (willRefundAmount.compareTo(availableAmount) > 0) {
                throw new RefundException("退款金额超过可退余额");
            }
        }
    }

    /**
     * 处理全额退款
     */
    private void processFullRefund(RefundContext context,
                                   String refundReason,
                                   String operator) throws RefundException {
        //当前可退金额
        BigDecimal availableAmount = context.getOrderRealAmount().subtract(context.getRefundedAmount());
        if (availableAmount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new RefundException("退款失败,可退余额不足!");
        }
        //本次剩余可退金额
        BigDecimal amount = context.getOrderRealAmount().subtract(context.getRefundedAmount());
//        if (amount.compareTo(context.getDriverBalance()) > 0) {//剩余可退金额大于司机余额
//            throw new RefundException("退款失败,司机余额余额不足!");
//        }
        // 退预付金额
        if (context.getPrepayOrder() != null) {
            //退剩余预付
            BigDecimal prepayAvailable = context.getPrepayOrder().getAmount()
                    .subtract(context.getPrepayRefundAmount());
            if (prepayAvailable.compareTo(BigDecimal.ZERO) > 0) {
                refundPrepayAmount(context.getPrepayOrder(), prepayAvailable);
                availableAmount = availableAmount.subtract(prepayAvailable);
            }
        }
        // 退补充金额
        if (context.getRideOrder().getFarePaid().compareTo(BigDecimal.ZERO) > 0) {
            refundSupplementAmount(context.getRideOrder(), availableAmount);
        }


        // 记录退款日志
        RideRefundLog refundLog = createRefundLog(context, generateRefundId(),
                amount,
                refundReason, operator);

        //修改订单信息
        RideOrder rideOrder = new RideOrder();
        rideOrder.setId(context.getRideOrder().getId());
        rideOrder.setRefunded(true);
        rideOrder.setActualAmount(BigDecimal.ZERO);
        rideOrder.setInfoServiceAmount(BigDecimal.ZERO);
        rideOrder.setRefundAmount(context.getRideOrder().getRefundAmount().add(amount));
        rideOrder.setDriverRefundAmount(context.getRideOrder().getDriverEarnAmount());
        if (ObjectUtil.isNotNull(context.getRideOrder().getCouponId())) {
            Coupon coupon = couponMapper.selectById(context.getRideOrder().getCouponId());
            if (ObjectUtil.isNotNull(coupon)) {
                Coupon updateCoupon = new Coupon();
                updateCoupon.setId(coupon.getId());
                updateCoupon.setHadUsed(false);
                couponMapper.updateById(updateCoupon);
            }
        }
        rideOrderMapper.updateById(rideOrder);
    }

    /**
     * 处理部分退款
     */
    private void processPartialRefund(RefundContext context,
                                      BigDecimal willRefundAmount,
                                      String refundReason,
                                      String operator) throws RefundException {
        BigDecimal remainingRefund = willRefundAmount;
        //当前订单剩余可退金额
        BigDecimal amount = context.getOrderRealAmount().subtract(context.getRefundedAmount());
//        if (amount.compareTo(context.getDriverBalance()) > 0) {//剩余可退金额大于司机余额
//            throw new RefundException("退款失败,司机余额不足!");
//        }
        //剩余可退预付金
        BigDecimal prepayAvailable = null;

        // 先退预付剩余部分
        if (context.getPrepayOrder() != null) {
            prepayAvailable = context.getPrepayOrder().getAmount()
                    .subtract(context.getPrepayRefundAmount());
            BigDecimal prepayRefund = prepayAvailable.min(remainingRefund);

            if (prepayRefund.compareTo(BigDecimal.ZERO) > 0) {
                refundPrepayPartial(context.getPrepayOrder(), prepayRefund);
                remainingRefund = remainingRefund.subtract(prepayRefund);
            }
        }

        // 退补充金额部分
        if (remainingRefund.compareTo(BigDecimal.ZERO) > 0) {
            refundSupplementPartial(context, remainingRefund, prepayAvailable);
        }

        // 记录退款日志
        RideRefundLog refundLog = createRefundLog(context, generateRefundId(),
                willRefundAmount,
                refundReason, operator);

        //跟新ride_order
        RideOrder rideOrder = new RideOrder();
        rideOrder.setId(context.getRideOrder().getId());
        rideOrder.setRefundAmount(context.getRideOrder().getRefundAmount().add(refundLog.getRefundAmount()));
        //TODO 遇到异常情况需要特殊处理 比如预付单有补钱 退的金额大于预付金额
        rideOrder.setDriverRefundAmount(context.rideOrder.getDriverRefundAmount().add(refundLog.getDriverRefundAmount()));
        if (amount.compareTo(willRefundAmount) > 0) {
            rideOrder.setActualAmount(context.rideOrder.getActualAmount().subtract(refundLog.getDriverRefundAmount()));
//            rideOrder.setPayAmount(context.rideOrder.getPayAmount().subtract(refundLog.getRefundAmount()));
            rideOrder.setInfoServiceAmount(context.rideOrder.getInfoServiceAmount().subtract(refundLog.getPlatformRefundAmount()));
        } else {
            rideOrder.setActualAmount(BigDecimal.ZERO);
            rideOrder.setDriverRefundAmount(context.rideOrder.getDriverEarnAmount());
            rideOrder.setInfoServiceAmount(BigDecimal.ZERO);
            rideOrder.setRefunded(true);
            if (ObjectUtil.isNotNull(context.getRideOrder().getCouponId())) {//退使用的优惠券
                Coupon coupon = couponMapper.selectById(context.getRideOrder().getCouponId());
                if (ObjectUtil.isNotNull(coupon)) {
                    Coupon updateCoupon = new Coupon();
                    updateCoupon.setId(coupon.getId());
                    updateCoupon.setHadUsed(false);
                    couponMapper.updateById(updateCoupon);
                }
            }
        }
        rideOrderMapper.updateById(rideOrder);
    }

    // region 数据库操作封装
    private Ride getRideById(Long rideId) {
        return rideMapper.selectById(rideId);
    }

    private RideOrder getRideOrderByRideId(Long rideId) {
        return rideOrderMapper.selectOne(
                new LambdaQueryWrapper<RideOrder>()
                        .eq(RideOrder::getRideId, rideId));
    }

    private List<RideRefundLog> getRefundLogs(Long rideId) {
        return rideRefundLogMapper.selectList(
                Wrappers.<RideRefundLog>lambdaQuery()
                        .eq(RideRefundLog::getRideId, rideId)
                        .eq(RideRefundLog::getState, REFUND_LOG_SUCCESS_STATE));
    }

    private Object getCall(Long callId, String category) {
        if ("realtime".equals(category)) {
            return realtimeCallMapper.selectById(callId);

        } else if ("appointment".equals(category)) {
            return appointmentCallMapper.selectById(callId);
        }
        return null;
    }

    private CallPrepayOrder getPrepayOrder(Long callId, String category) {
        LambdaQueryWrapper<CallPrepayOrder> wrapper = new LambdaQueryWrapper<>();
        if ("realtime".equals(category)) {
            wrapper.eq(CallPrepayOrder::getCallCategory, 0);
        } else if ("appointment".equals(category)) {
            wrapper.eq(CallPrepayOrder::getCallCategory, 1);
        }
        wrapper.eq(CallPrepayOrder::getCallId, callId);
        return callPrepayOrderMapper.selectOne(wrapper);
    }
    // endregion

    // region 金额计算
    private BigDecimal calculateRefundedAmount(List<RideRefundLog> refundLogs) {
        return refundLogs.stream()
                .map(RideRefundLog::getRefundAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private BigDecimal calculatePrepayRefund(CallPrepayOrder prepayOrder) {
        return prepayOrder != null ?
                callPrepayOrderRefundLogMapper.selectRefundAmount(prepayOrder.getId()) :
                BigDecimal.ZERO;
    }

    private BigDecimal calculateOrderRealAmount(RideOrder rideOrder,
                                                CallPrepayOrder prepayOrder,
                                                BigDecimal prepayRefund) {
        if (prepayOrder == null) {
            return rideOrder.getFarePaid();
        }
        return rideOrder.getFarePaid()
                .add(prepayOrder.getAmount());
    }
    // endregion

    // region 支付操作
    private void refundSupplementAmount(RideOrder rideOrder, BigDecimal availableAmount) throws RefundException {
        executeCmbRefund(rideOrder.getNumber(),
                rideOrder.getFarePaid(),
                availableAmount);
    }

    private void refundPrepayAmount(CallPrepayOrder prepayOrder, BigDecimal prepayAvailable) throws RefundException {
        executeCmbRefund(prepayOrder.getNumber(),
                prepayOrder.getAmount(),
                prepayAvailable);
        updatePrepayOrderStatus(prepayOrder);
    }

    private void refundPrepayPartial(CallPrepayOrder prepayOrder,
                                     BigDecimal amount) throws RefundException {
        executeCmbRefund(prepayOrder.getNumber(),
                prepayOrder.getAmount(),
                amount);
        createPrepayRefundLog(prepayOrder, amount);
    }

    private void refundSupplementPartial(RefundContext context,
                                         BigDecimal amount,
                                         BigDecimal prepayAvailable) throws RefundException {
        RideOrder rideOrder = context.getRideOrder();
        try {
            executeCmbRefund(rideOrder.getNumber(),
                    rideOrder.getFarePaid(),
                    amount);
        } catch (RefundException e) {
            if (ObjectUtil.isNotNull(prepayAvailable) && prepayAvailable.compareTo(BigDecimal.ZERO) > 0) {
                createRefundLog(context, generateRefundId(),
                        prepayAvailable,
                        "退预付", context.getOperator());
            }
            throw new RefundException("退款出现异常,只成功退了预付款!");
        }
    }

    private void executeCmbRefund(String orderNumber,
                                  BigDecimal totalAmount,
                                  BigDecimal refundAmount) throws RefundException {

        String refundId = generateRefundId();
        Map<String, String> result = CmbPayClient.refund(
                refundId,
                orderNumber,
                "退款",
                orderNumber,
                refundId,
                Convert.toStr(totalAmount.multiply(HUNDRED).intValue()),
                Convert.toStr(refundAmount.multiply(HUNDRED).intValue())
        );

        if (!PAYMENT_SUCCESS.equals(result.get("respCode"))) {
            log.error("支付接口返回错误: {},退款订单信息：orderNumber：{}", result.get("respMsg"), orderNumber);
            throw new RefundException(StrUtil.format("支付接口返回错误: {}", result.get("respMsg")));
        }
    }
    // endregion

    // region 记录操作
    private RideRefundLog createRefundLog(RefundContext context, String refundId,
                                          BigDecimal amount, String reason, String operator) {
        BigDecimal beforeBalance = context.getDriverBalance();
        BigDecimal afterBalance = context.getDriverBalance().subtract(amount);
        RideRefundLog logEntry = RideRefundLog.builder()
                .rideId(context.getRide().getId())
                .rideOrderId(context.getRideOrder().getId())
                .customerId(context.getRide().getUserId())
                .companyId(context.getRide().getCompanyId())
                .cantonId(context.getRide().getCantonId())
                .orderNumber(context.getRideOrder().getNumber())
                .number(refundId)
                .reason(reason)
                .orderAmount(context.getRideOrder().getAmount())
                .refundAmount(amount)
                .state(REFUND_LOG_SUCCESS_STATE)
                .operator(operator)
                .driverRefundRatio(context.getDriverRefundRatio())
                .companyRefundRatio(context.getCompanyRefundRatio())
                .platformRefundRatio(context.getPlatformRefundRatio())
                .driverRefundAmount(calculateDriverRefund(context, amount))
                .platformRefundAmount(calculatePlatformRefund(context, amount))
                .applyTime(context.applyTime)
                .completeTime(DateUtil.now())
                .refundBeforeBalance(beforeBalance)
                .refundAfterBalance(afterBalance)
                .build();

        rideRefundLogMapper.insert(logEntry);


        return logEntry;
    }

    private void createPrepayRefundLog(CallPrepayOrder order, BigDecimal amount) {
        CallPrepayOrderRefundLog logEntry = new CallPrepayOrderRefundLog()
                .setOrderId(order.getId())
                .setIsSuccess(true)
                .setRefundAmount(amount)
                .setRefundNumber(generateRefundId());
        callPrepayOrderRefundLogMapper.insert(logEntry);
    }

    private void updatePrepayOrderStatus(CallPrepayOrder order) {
        callPrepayOrderMapper.updateById(
                new CallPrepayOrder().setId(order.getId()).setIsRefund(true));
    }
    // endregion

    // region 工具方法
    private BigDecimal calculateDriverRefund(RefundContext context, BigDecimal amount) {
        return amount.multiply(context.getDriverRefundRatio()).setScale(2, RoundingMode.HALF_UP);
    }

    private BigDecimal calculatePlatformRefund(RefundContext context, BigDecimal amount) {
        return amount.multiply(context.getPlatformRefundRatio()).setScale(2, RoundingMode.HALF_UP);
    }

    private String generateRefundId() {
        return new Identifier("R").generate();
    }
    // endregion

    // region 上下文对象
    @Getter
    @Setter
    @Builder
    @Accessors(chain = true)
    private static class RefundContext {
        private Ride ride;
        private String category;
        private RideOrder rideOrder;
        private RealtimeCall realtimeCall;
        private AppointmentCall appointmentCall;
        private CallPrepayOrder prepayOrder;
        private BigDecimal refundedAmount;
        private BigDecimal prepayRefundAmount;
        private BigDecimal orderRealAmount;
        private String operator;
        private BigDecimal driverBalance;
        private String applyTime;
        private BigDecimal driverRefundRatio; // 司机退款比率
        private BigDecimal companyRefundRatio; // 公司退款比率
        private BigDecimal platformRefundRatio; // 平台退款比率
    }
    // endregion

    // region 自定义异常
    private static class RefundException extends Exception {
        public RefundException(String message) {
            super(message);
        }
    }

    public static class BusinessException extends RuntimeException {
        public BusinessException(String message) {
            super(message);
        }
    }
    // endregion
}